using System;
using Heaven.Mathematics;

namespace Heaven.Engine
{
    /// <summary>
    /// Abstract class of a virtual camera
    /// </summary>
    public abstract class Camera
    {
        #region Events

        // Weak events
        WeakMulticastDelegate invalidated = new WeakMulticastDelegate();

        /// <summary>
        /// Occurs when the camera has been changed
        /// </summary>
        public event EventHandler Invalidated
        {
            add { invalidated.Add(value); }
            remove { invalidated.Remove(value); }
        }

        #endregion

        #region Fields

        // Cache
        bool viewProjectionCached = false;
        bool viewInverseCached = false;
        bool projectionInverseCached = false;
        bool viewProjectionInverseCached = false;
        bool viewTransposeCached = false;
        bool projectionTransposeCached = false;
        bool viewProjectionTransposeCached = false;
        bool viewInverseTransposeCached = false;
        bool projectionInverseTransposeCached = false;
        bool viewProjectionInverseTransposeCached = false;

        Matrix4 viewProjection;
        Matrix4 viewInverse;
        Matrix4 projectionInverse;
        Matrix4 viewProjectionInverse;
        Matrix4 viewTranspose;
        Matrix4 projectionTranspose;
        Matrix4 viewProjectionTranspose;
        Matrix4 viewInverseTranspose;
        Matrix4 projectionInverseTranspose;
        Matrix4 viewProjectionInverseTranspose;

        #endregion

        #region Properties


        /// <summary>
        /// The final matrix of view
        /// </summary>
        public abstract Matrix4 View
        {
            get;
        }

        /// <summary>
        /// The final projection matrix
        /// </summary>
        public abstract Matrix4 Projection
        {
            get;
        }

        #endregion

        #region Cached matrices

        /// <summary>
        /// ViewProjection matrix
        /// </summary>
        public Matrix4 ViewProjection
        {
            get
            {
                if (!viewProjectionCached)
                {
                    viewProjection = View * Projection;
                    viewProjectionCached = true;
                }
                return viewProjection;
            }
        }

        /// <summary>
        /// ViewInverse matrix
        /// </summary>
        public Matrix4 ViewInverse
        {
            get
            {
                if (!viewInverseCached)
                {
                    viewInverse = Matrix4.Inverse(View);
                    viewInverseCached = true;
                }
                return viewInverse;
            }
        }

        /// <summary>
        /// ProjectionInverse matrix
        /// </summary>
        public Matrix4 ProjectionInverse
        {
            get
            {
                if (!projectionInverseCached)
                {
                    projectionInverse = Matrix4.Inverse(Projection);
                    projectionInverseCached = true;
                }
                return projectionInverse;
            }
        }

        /// <summary>
        /// ViewProjectionInverse matrix
        /// </summary>
        public Matrix4 ViewProjectionInverse
        {
            get
            {
                if (!viewProjectionInverseCached)
                {
                    viewProjectionInverse = Matrix4.Inverse(ViewProjection);
                    viewProjectionInverseCached = true;
                }
                return viewProjectionInverse;
            }
        }

        /// <summary>
        /// ViewTranspose matrix
        /// </summary>
        public Matrix4 ViewTranspose
        {
            get
            {
                if (!viewTransposeCached)
                {
                    viewTranspose = Matrix4.Transpose(View);
                    viewTransposeCached = true;
                }
                return viewTranspose;
            }
        }

        /// <summary>
        /// ProjectionTranspose matrix
        /// </summary>
        public Matrix4 ProjectionTranspose
        {
            get
            {
                if (!projectionTransposeCached)
                {
                    projectionTranspose = Matrix4.Transpose(Projection);
                    projectionTransposeCached = true;
                }
                return projectionTranspose;
            }
        }

        /// <summary>
        /// ViewProjectionTranspose matrix
        /// </summary>
        public Matrix4 ViewProjectionTranspose
        {
            get
            {
                if (!viewProjectionTransposeCached)
                {
                    viewProjectionTranspose = Matrix4.Transpose(ViewProjection);
                    viewProjectionTransposeCached = true;
                }
                return viewProjectionTranspose;
            }
        }

        /// <summary>
        /// ViewInverseTranspose matrix
        /// </summary>
        public Matrix4 ViewInverseTranspose
        {
            get
            {
                if (!viewInverseTransposeCached)
                {
                    viewInverseTranspose = Matrix4.Transpose(ViewInverse);
                    viewInverseTransposeCached = true;
                }
                return viewInverseTranspose;
            }
        }

        /// <summary>
        /// ProjectionInverseTranspose matrix
        /// </summary>
        public Matrix4 ProjectionInverseTranspose
        {
            get
            {
                if (!projectionInverseTransposeCached)
                {
                    projectionInverseTranspose = Matrix4.Transpose(ProjectionInverse);
                    projectionInverseTransposeCached = true;
                }
                return projectionInverseTranspose;
            }
        }

        /// <summary>
        /// ViewProjectionInverseTranspose matrix
        /// </summary>
        public Matrix4 ViewProjectionInverseTranspose
        {
            get
            {
                if (!viewProjectionInverseTransposeCached)
                {
                    viewProjectionInverseTranspose = Matrix4.Transpose(ViewProjectionInverse);
                    viewProjectionInverseTransposeCached = true;
                }
                return viewProjectionInverseTranspose;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Invalidates object
        /// </summary>
        public virtual void Invalidate()
        {
            viewProjectionCached = false;
            viewInverseCached = false;
            projectionInverseCached = false;
            viewProjectionInverseCached = false;
            viewTransposeCached = false;
            projectionTransposeCached = false;
            viewProjectionTransposeCached = false;
            viewInverseTransposeCached = false;
            projectionInverseTransposeCached = false;
            viewProjectionInverseTransposeCached = false;
            invalidated.Invoke(this, EventArgs.Empty);
        }

        /// <summary>
        /// Determines if the given point visible from this virtual camera
        /// </summary>
        /// <param name="point">A point</param>
        /// <returns>True if the point is visible</returns>
        public virtual bool IsVisible(Vector3 point)
        {
            return true;
        }

        #endregion
    }
}
